#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <unistd.h>
#include <sys/mman.h>
#include "util.h"

#define NUM_VALUES 100
#define NUM_OPS 1000000000
#define TYPE char
#define MAP_HUGETLB 0x40000
//#define MAP_HUGETLB 0

pthread_mutex_t LOCK;

using namespace std;

typedef unsigned long long uint64;

uint64 NUM_THREADS;
const uint64 L_SIZE = 5;
const uint64 SIZE = 1ull << L_SIZE;
const uint64 SIZE_MASK = SIZE - 1;
const uint64 OUTER = 1;
const uint64 REPS = (1ull << 27);
uint64 ** DATA;

int THREADS_DONE;

void *ThreadBody(void *threadid)
{
  long tid;
  tid = (long)threadid;
  uint64 sum = 0;
  cout << "Start thread " << pthread_self() << endl;
  void * alloc
    = new uint64[REPS];
  int64 start = TimeNS();
    /*    = (uint64 *)
    mmap(NULL, (REPS * sizeof(uint64)),
    PROT_READ | PROT_WRITE, 
    MAP_HUGETLB | MAP_ANON | MAP_PRIVATE | MAP_NORESERVE, 
    -1, 0); // last two parameters ignored)*/
  cout << "Allocated " << alloc << endl;
  DATA[tid] = (uint64*)alloc;
  

  uint64 multiplier = (tid + 1) * 22342394 + 1;
  uint64 *data = DATA[tid];
  /*  for (uint64 rep = 0; rep < REPS; rep ++) {
    data[rep] = rep;
  }
  start = time(0);*/
  for (uint64 x=0; x<OUTER; x++) {
    for (uint64 rep = 0; rep < REPS; rep+= 512) {
      //sum += data[rep];
      data[rep] = rep;
      /*    uint64 loc = (rep * multiplier) & SIZE_MASK;
      //sum += __sync_add_and_fetch(DATA[tid] + loc, tid);
      //pthread_mutex_lock(&LOCK);
      if ((rep & 0xFFFFFull) == 0) 
      DATA[tid][loc] += 1;
      sum += DATA[tid][loc];
      //pthread_mutex_unlock(&LOCK);
      */
    }
}
  cout << "sum = " << sum << " " << endl;
  // cout << "End thread " << pthread_self() << endl;
  cout << "Time: " << (TimeNS() - start) << endl;
  THREADS_DONE++;
  delete [] DATA[tid];
  pthread_exit(NULL);  
}
 
int main (int argc, char *argv[])
{
  pthread_mutex_init(&LOCK, NULL);
  THREADS_DONE = 0;
  NUM_THREADS = atoi(argv[1]);
  DATA = new uint64* [NUM_THREADS];
  /*
  uint64 * SAME_DATA = new uint64[SIZE];
  for (uint64 i=0; i<NUM_THREADS; i++) {
    DATA[i] = SAME_DATA;
    // DATA[i] = new uint64[SIZE];
    // for (uint64 j=0; j<SIZE; j++) DATA[i][j] = j;
  }
  */
  pthread_t threads[NUM_THREADS];
  
  int rc;
  long t;
   for(t=0; t<NUM_THREADS; t++){
     printf("In main: creating thread %ld\n", t);
     rc = pthread_create(&threads[t], NULL, ThreadBody, (void *)t);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
     }
   }
   int x, y;
   // while (cin >> x >> y) cout << DATA[x][y] << endl;
   pthread_exit(NULL);
}

/*

timing: 
-------- MBP ----------

2 threads * 1M contended locks = 4.5s                       
5 threads * 1M contended locks = 24s                          [5000 ns]
25 threads * 100K contended locks = 11.7s                     [5000 ns]
2 threads * 100M uncontended locks = 7.5s (.7s without locks)  [75 ns]

------ Amazon machine ---------
100 threads * 1M contended locks = 10.7s     [100ns]
8 threads * 100M uncontended locks = 16.2s   [160ns]

 */
